Skip to content

ci: complete pre-commit.ci replacement and cache Lychee checks - #2993

Draft
rwgk wants to merge 18 commits into
mainfrom
rwgk/maint/lychee-pr-nightly
Draft

rwgk wants to merge 18 commits into
mainfrom
rwgk/maint/lychee-pr-nightly

Conversation

@rwgk

@rwgk rwgk commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary and narration

This PR completes the workflow changes for replacing pre-commit.ci with GitHub Actions, following #2652. #2994 already introduced the standalone Linux and Windows pre-commit checks and monthly Dependabot hook updates. This PR removes the remaining duplication, keeps link checking in the existing full-CI documentation build, and adds a nightly cache to reduce repeated requests and make Lychee less sensitive to temporary website failures.

An ordinary PR update runs pre-commit on Linux and Windows against the PR head, skipping Lychee as pre-commit.ci did. It does not start an additional documentation build. When full CI runs through copy-pr-bot, the existing documentation job builds from CI's wheels and then checks both authored documentation and rendered HTML. The documentation job keeps its existing PR-head checkout and wheel inputs. The resulting status flows through the existing Check job status requirement.

Nightly builds documentation afresh and warms the shared cache without restoring yesterday's results. Successful external-link checks can then be reused for up to one day; local files and HTML anchors are checked against each fresh build. Both paths allow at most three Lychee passes. Full CI tolerates a small number of rate-limited URLs, while nightly treats unresolved links as warnings so that cache warming does not make the existing nightly suite depend on every external website being available. The policy and its deliberate enforcement changes are explained first below.

Ralf will coordinate with a repo admin to add the two standalone pre-commit requirements shortly after this PR merges, then remove the old pre-commit.ci requirement and the service's access to cuda-python. The existing full-CI and PR-metadata requirements remain. The final expandable section contains the one-time admin instructions.

Lychee retry, warning, and failure policy

Three passes, retaining successful checks

Each authored or rendered check permits at most three total passes, including the initial pass. Lychee also retains its existing maximum of three internal retries per request. Another complete pass runs only when every remaining failure is a recognized transient HTTP(S) failure: status 408, 429, or 5xx, or an HTTP(S) timeout. A permanent or unclassified failure stops additional passes, including when it occurs alongside transient failures. Cooldowns are 60 seconds before pass two and 120 seconds before pass three: at most three minutes of outer cooldowns.

Every pass uses the same pinned binary, arguments, inputs, and accumulated cache. Successful external URLs therefore avoid repeated network requests, while unresolved URLs are checked again. Failed responses are never recorded as successful cache entries. The three-pass limit bounds repeated checking; the existing 60-minute step/job limits bound unexpectedly long execution.

The final result depends on the purpose of the run

After checking succeeds, retries stop, or the third pass finishes:

Final valid link-check result Full PR CI Nightly cache warming
All links checked successfully Pass Pass and publish successful checks
Only HTTP 429s remain, affecting at most 10 distinct URLs Pass with a warning listing the unverified URLs Warn and publish successful checks
More than 10 distinct URLs still return HTTP 429 Fail Warn and publish successful checks
Any other link failure remains, such as a 404, missing file, or invalid anchor Fail Warn and publish successful checks
Documentation build, checker setup, input preparation, execution, or report validation fails Fail Fail

HTTP 429 means the server rate-limited the request; it does not establish whether the target page exists. Allowing up to 10 such URLs in full CI is an intentional relaxation of the previous link-checking policy. Those URLs remain unverified. The limit applies separately to each authored/rendered invocation and counts exact HTTP(S) URL strings in the final report, deduplicated across source files. Repeated appearances count once.

Nightly's purpose is to build a useful cache, so valid unresolved link results are advisory, including permanent failures that stopped retries early. A partial cache reduces network exposure for later PR checks; it does not guarantee that uncached links will succeed.

Completed link-check results, including warnings and unresolved failures, appear in the job summary. Available per-pass JSON reports and the final summary are retained as seven-day artifacts. A missing, malformed, or empty report, inconsistent exit code, or other checker failure is never converted into a link warning. These are failures of the checking machinery, not incomplete cache coverage.

Details for all reviewers

Where each check runs

Entry point Behavior
PR opened, reopened, or updated Standalone Pre-commit (Linux) and Pre-commit (Windows) run against the PR head with Lychee skipped. No documentation build is added. Draft and fork PRs follow the same trigger, subject to GitHub's normal workflow-approval settings.
Full CI on pull-request/<number> Existing package builds, tests, documentation rendering, and preview deployment run. After rendering, authored and rendered links are checked with the shared action and cache.
Existing nightly schedule A fresh source documentation build and link sweep publish new cache snapshots alongside the existing nightly suite.
Manual dispatch The standalone pre-commit workflow can run directly. Lychee supports cache-reader and cache-refresh modes; nightly also has a documentation-only mode that skips wheel and GPU jobs.

The standalone pre-commit checks run independently of /ok to test and [no-ci]. Full-CI link checking follows the existing CI controls. The dedicated Lychee workflow is reusable and manually dispatchable; it has no PR or push trigger. There is no additional documentation build when a PR is copied to the full-CI ref.

The pre-commit workflow explicitly checks out the PR head, retains Python 3.14 and pip upgrading, and enables Windows symlinks before checkout. The old Windows job and its dependency are removed from ci.yml, since the standalone workflow supplies its replacement. Local hook configuration and behavior are unchanged; contributors continue to use pre-commit install and pre-commit run --all-files as before. The remaining pre-commit.ci configuration can stay in place through the administrative cutover.

One checker shared by full CI and nightly

build-docs.yml invokes the check-doc-links composite action twice after building PR documentation: once for authored files and once for rendered HTML. Nightly invokes the same action from its authored/rendered matrix. Sharing the action keeps input selection, the Lychee pin, request limits, cache handling, and reports consistent, while selecting the appropriate final-result policy for each caller.

Authored inputs are tracked .md and .rst files outside qa/, excluding symlinks. Rendered inputs are HTML files under artifacts/docs, excluding _static assets, with full fragment validation enabled. Input preparation produces deterministic absolute-path lists, preserves spaces, and rejects empty lists or filenames containing line breaks. Lychee stays pinned to v0.24.2, matching the local hook. The composite action requires Python 3.10+ on PATH to run its standard-library helpers.

Full CI retains the documentation build's existing wheel inputs, checkout, publication settings, and preview URL exclusions. Link checking remains restricted to non-release pull-request/<number> builds, as the previous rendered check was. Authored Markdown/reST checking is additional full-CI coverage, including files untouched by the PR. An enforced link failure fails Docs, which already feeds the required Check job status gate; no separate Documentation links requirement is needed.

The local link sweep also found moved documentation and sample pages referenced by seven sample READMEs. A separate commit updates those references to verified destinations without adding exclusions.

Nightly's rendered job uses pixi run --manifest-path cuda_core/pixi.toml -e docs docs-build-all-latest to assemble the latest documentation for all four packages from checked-out sources. PIXI_FROZEN=true installs the committed docs environment without re-solving dependencies or requiring workspace-wide lockfile freshness. The build helper verifies local package imports and version metadata, installs the metapackage without re-resolving dependencies, and invokes the existing documentation scripts. This checking build uses local file:// canonical targets so unpublished pages can be checked before they exist on the public site. The wheel-based publication path keeps its existing canonical URLs.

Cache behavior and diagnostics

Authored and rendered checks have separate caches because rendered checking also validates fragments. Each namespace includes the Lychee version and a hash of the action, configuration, and helper scripts. Ordinary documentation edits preserve reusable external-link results; checking-policy changes select a new namespace. Both workflow callers use the same cache paths and key namespaces.

Full-CI runs restore a matching snapshot and do not publish one. Nightly skips restoration and publishes a new immutable snapshot keyed by run ID and attempt, including when some links remain unresolved. Successful external checks expire after one day. Lychee v0.24.2 omits failed responses from its persisted cache and does not cache filesystem checks. Those failures and local files are therefore checked again on the next run.

After merge, nightly snapshots from main can be restored by other branches under GitHub Actions' cache access rules. Manual refreshes on a feature branch remain scoped to that branch. Per-host caching for interactive development remains a separate follow-on.

Existing request limits are retained: 16 concurrent requests overall, two per host, a 250 ms host request interval, three per-request retries, and a 30-second request timeout. The retry helper validates the first action report and applies the retry and final-result policy described above. It requires a positive checked-link count, validates the JSON report and CLI exit-code agreement, and rejects machinery failures before considering warning allowances.

ci-nightly.yml keeps the reusable checker in its existing nightly gate: link warnings pass, while failures to build or run the checker still fail the gate. Documentation-only mode also runs the standalone CI-tool tests and checks that wheel/GPU jobs were skipped. The helper tests exercise input preparation, canonical-link build setup, transient classification, retry recovery and exhaustion, warning thresholds, mixed failures, cache/argument reuse, and report validation.

Validation and full CI coverage

The validated PR head is 54ee0bb, incorporating main through 106da2f. This was current main immediately before publication; main advanced while validation ran. All workflow results below refer to the stated revision and base.

Evidence Result Coverage
Standalone pre-commit Passed on the first attempt Actual PR-event Linux/Windows checks, PR-head checkout, Windows symlink setup, and skipped Lychee.
Full CI Passed: all 107 jobs, including the final gate, on the first attempt Existing package builds and tests, wheel-based documentation rendering, both shared-action link checks, preview deployment, and the required Check job status gate.
Nightly documentation-only producer Passed on the first attempt Reusable nightly call, fresh source documentation build, fresh checking, publication of authored/rendered caches, CI-tool tests, and nightly gating.
Cache reader Passed on the first attempt Restoration of producer snapshots, fresh local-file/anchor checking, and skipped cache publication.

Full CI exercised the cold-cache path: both authored and rendered link checks passed on their first pass. The nightly producer checked 273 authored files and 1,116 HTML files, also passing both checks on their first pass with no unresolved failures. The independent reader restored the exact authored and rendered cache keys published by that producer, passed both checks on their first pass, and skipped cache publication. As an observation from these runs, Lychee's authored checking took about 152 seconds cold versus 0.022 seconds warm, and rendered checking took about 158 seconds cold versus 26 seconds warm; these timings exclude documentation building. The nightly CI-tool tests and final gate also passed.

Local validation used the existing Pixi on PATH: all pre-commit hooks passed, all 220 standalone CI-tool tests and 36 subtests passed, and a complete source documentation build succeeded with the committed dependency versions. Real Lychee v0.24.2 loopback tests covered the 10/11-URL warning boundary, duplicate URLs across sources, mixed 429/404 failures, nightly 404 warnings, and a broken local anchor with a warm external cache. They confirmed that successful requests were reused and failed URLs were absent from the persisted cache. The harness recorded the 60/120-second cooldowns without waiting and disabled native per-request retries to keep these policy tests fast; production settings were unchanged.

Together, full CI exercises the production PR path and merge gate; the focused nightly/reader runs exercise cache production and reuse; and local tests cover warning and failure paths that a successful live run need not encounter. Default-branch cache sharing across PRs requires a post-merge nightly run on main; branch-scoped producer/reader tests cannot establish that cross-branch behavior before merge.

Details for admin updating the Rulesets

Required-check changes

The current Prevent committing without PR ruleset requires pre-commit.ci - pr, Check job status, and PR has assignee, labels, and milestone. It applies to main, 12.9.x, 11.8.x, 13.4.x, and release/**/*.

Ralf and Leo will coordinate the cutover shortly after #2993 merges. Add the two standalone pre-commit checks in a new main-only Ruleset, then remove the retired pre-commit.ci requirement from the shared Ruleset. GitHub combines applicable Rulesets, so this briefly creates five required contexts before leaving four, all from GitHub Actions. Link checking is covered by Check job status; do not add a separate Documentation links requirement. This is a one-time manual cutover; the helper preserved in closed #3002 is not needed.

  1. Verify the replacements. Confirm successful Pre-commit (Linux), Pre-commit (Windows), and full-CI results on this PR. Keep pre-commit.ci installed and required until the replacement requirements have been configured.

  2. Add a main-only Ruleset shortly after merging ci: complete pre-commit.ci replacement and cache Lychee checks #2993. In repository Settings > Rules > Rulesets, choose New ruleset > New branch ruleset. Name it, for example, Main pre-commit checks, set enforcement to Active, and target only main. Enable Require status checks to pass before merging and add Pre-commit (Linux) and Pre-commit (Windows), selecting GitHub Actions as the expected source for each (integration ID 15368). Match the existing policy: leave Require branches to be up to date before merging unchecked and keep branch creation exempt. Leave the bypass list empty and enable only the status-check rule. Save.

  3. Verify the merged workflow and remove the old requirement. On a PR based on updated main, verify that full CI's Docs runs both link checks and that Check job status passes. Then edit Prevent committing without PR and remove only pre-commit.ci - pr (integration ID 68672). Preserve its other checks, branch targets, review requirements, and protections.

  4. Verify enforcement. A PR targeting main should show the four requirements below, with no pre-commit.ci requirement. On a temporary test PR, a formatting failure should block merging through pre-commit, and a broken link other than an allowed 429 should block merging through Docs and Check job status. Repair the errors and verify success. Check the merge box; draft status alone is not evidence of required-check enforcement. Release branches retain their two existing Actions requirements and do not gain the new pre-commit requirements until the workflow changes are deliberately backported.

  5. Remove the repository from pre-commit.ci's access. Open Settings > Integrations > GitHub Apps, choose Configure for pre-commit.ci, remove only cuda-python from selected repository access, and save. Preserve access for other NVIDIA repositories. If the installation covers All repositories, coordinate with an organization owner to retain the other repositories when changing to selected access. Do not suspend or uninstall the shared installation. GitHub's repository-access instructions

Required check on main Ruleset Expected source
Check job status Existing shared Ruleset GitHub Actions
PR has assignee, labels, and milestone Existing shared Ruleset GitHub Actions
Pre-commit (Linux) New main-only Ruleset GitHub Actions
Pre-commit (Windows) New main-only Ruleset GitHub Actions

For an optional read-only confirmation:

gh api repos/NVIDIA/cuda-python/rules/branches/main --jq '.[] | select(.type == "required_status_checks") | .parameters.required_status_checks[] | {context, integration_id}'

The output should contain those four contexts, each with integration ID 15368. Editing Rulesets requires repository admin access or permission to edit repository rules; changing the organization's App installation may require additional installation-management access.

@rwgk rwgk added this to the cuda.core next milestone Oct 1, 2026
@rwgk rwgk added the CI/CD CI/CD infrastructure label Oct 1, 2026
@copy-pr-bot

copy-pr-bot Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@rwgk rwgk self-assigned this Oct 1, 2026
@github-actions github-actions Bot added the cuda.core Everything related to the cuda.core module label Oct 1, 2026
@rwgk
rwgk added this pull request to stack #2995 October 1, 2026 23:37
@rwgk
rwgk force-pushed the rwgk/maint/lychee-pr-nightly branch from 2c1f18e to b6d4565 Compare October 2, 2026 17:00
@rwgk rwgk mentioned this pull request Oct 2, 2026
2 tasks done
@rwgk

rwgk commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

EDIT: The links below are outdated, but the guidance in general is still useful.

I asked codex to guide me through reviewing this PR myself. Sharing what it gave me (in part because I want to click the links here myself):

Review Files changed against #2994; that isolates this layer. I’d use six passes, each with one question to answer before moving on.

  1. When does it run, and what does it check?
    Start with lychee.yml’s triggers and checkout. Trace a PR update, nightly call, and manual run. There is no push trigger, so copy-pr-bot’s branch pushes do not add another run. Checkout selects the PR’s head SHA.

  2. Are we selecting the intended documentation?
    Read the input selector, then its tests. Authored inputs are tracked Markdown/reST outside qa/; rendered inputs are HTML outside _static. Pay particular attention to skipping README symlinks: checking the same content from another directory can misinterpret relative links. Empty inputs must fail.

  3. Does rendered checking use this PR’s sources?
    Follow the build helper. The important safeguards are verifying that all three libraries import from the checkout and match their installed metadata. Installing metapackage metadata with --no-deps preserves those local packages.

  4. Does caching preserve the checking policy?
    Read cache preparation through publication. Trace both values of refresh-cache: PRs restore; nightly checks afresh and publishes. Version, policy, and authored/rendered kind separate the namespaces; successful URL checks expire after one day. Shared visibility relies on publishing from main, under GitHub’s cache scope rules.

  5. Can failures accidentally produce a green check?
    Examine the checker and aggregate gate, then the nightly gate. Think through a failed docs build, one failed matrix leg, cancellation, and documentation-only mode. That mode requires successful link/tool checks and skipped wheel/GPU jobs.

  6. Do the deletions have complete replacements?
    Finish with build-docs.yml, ci.yml, and the documentation changes. The dedicated workflow replaces inline lychee; [no-ci] ci: run pre-commit on Linux and Windows with Dependabot hook updates #2994 replaces Windows pre-commit. Live required-check migration belongs to ci: add required-check migration helper #3002.

Base automatically changed from rwgk/maint/pre-commit-actions to main October 2, 2026 18:46
@rwgk
rwgk force-pushed the rwgk/maint/lychee-pr-nightly branch from b687fa4 to c3eb6e3 Compare October 2, 2026 18:46
@leofang
leofang self-requested a review October 3, 2026 02:38
@rwgk
rwgk force-pushed the rwgk/maint/lychee-pr-nightly branch from c3eb6e3 to 375be61 Compare October 3, 2026 04:05
@rwgk

rwgk commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 27b3cf9

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

@rwgk

rwgk commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test ed21592

@rwgk

rwgk commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test d15a151

@rwgk
rwgk marked this pull request as ready for review October 4, 2026 04:25
@rwgk
rwgk requested a review from mdboom October 4, 2026 22:59
@rwgk
rwgk force-pushed the rwgk/maint/lychee-pr-nightly branch from d15a151 to c97d02b Compare October 6, 2026 07:09

@mdboom mdboom left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my agent. If true, this seems pretty serious -- it means all new internal links added to the docs would fail the lychee test. We can test whether it is correct by adding a new page to the docs and a link to it.

The script builds with BUILD_LATEST=1 and BUILD_PREVIEW=0. I checked how that plays out in the doc configs and lychee:

cuda_core/docs/source/conf.py and cuda_python/docs/source/conf.py set html_baseurl to https://nvidia.github.io/cuda-python/<pkg>/latest/ when BUILD_LATEST=1. Sphinx emits <link rel="canonical"> from it on every page.
Lychee 0.24.2 only skips preconnect and dns-prefetch link rels (html5ever.rs:179-182), so canonical URLs are extracted and checked.
The old flow built with BUILD_PREVIEW=1, which is why lychee.toml:7 excludes pr-preview/pr-N/ URLs. That exclusion no longer matches anything this workflow produces.
The failure scenario is a PR that adds a new doc page, such as a new release-notes file or a new API page. Its canonical URL points at the production latest/ site, where the page doesn't exist until after merge and deploy. That is a 404, which retry_lychee.py correctly treats as permanent. Documentation links goes red, and once it is a required check the PR can't merge. The PR's own validation couldn't have caught this, because it adds no doc pages.

Possible fixes:

  • Set CUDA_PYTHON_DOCS_DOMAIN to an excluded or local host (for example a file:// or .invalid domain) and exclude it in lychee.toml.
    Build with BUILD_PREVIEW=1 and a dummy PR_NUMBER, so the existing exclusion applies.
    Add an exclusion for ^https://nvidia\.github\.io/cuda-python/(latest|cuda-[a-z]+/latest)/.

====

Also from my agent, it seems like the cooldown, since it starts from scratch each time, is not going to be particularly effective.

timeout-minutes: 60 can cut off the "ten passes" guarantee for the rendered job. The job includes a docs build and up to 9 cooldowns, 60s then 120s each, which is about 17 minutes of sleeping. Every retry pass re-checks all local and file:// targets, since the PR notes that filesystem checks aren't cached. That is about 740k occurrences in the rendered tree, and the pass duration isn't stated. If a pass takes more than 4–5 minutes, a persistent 429 hits the timeout and the job is cancelled instead of reaching the clean "after 10 attempts" report. It still fails, but the summary is lost and the cause looks different. Either raise the timeout or document the observed pass duration.

====

Also from my agent. It called this "MEDIUM", but it seems pretty serious to me since it won't detect logical merge issues which are a large reason the lychee check exists.

Merge ref: Checking out pull_request.head.sha means the PR is checked without being merged into current main, unlike pre-commit.yml (default merge-ref checkout). I understand the reason: CUDA_PYTHON_DOCS_GITHUB_REF needs a SHA that exists upstream. It does mean that a main-side change, such as a page removed on main, isn't caught until after merge. This is probably acceptable, but a one-line comment would help.

@rwgk

rwgk commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Converting to Draft mode before rebasing to fetch the latest updates from main. I don't want to trigger CI, only get a clean baseline for addressing @mdboom's feedback.

@rwgk
rwgk marked this pull request as draft October 6, 2026 19:21
@rwgk
rwgk force-pushed the rwgk/maint/lychee-pr-nightly branch 2 times, most recently from 14c2b8d to 7fbf0b6 Compare October 6, 2026 19:23
@rwgk

rwgk commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 081ded4

@rwgk

rwgk commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

@mdboom the CI is really slow today, but it looks like all jobs will be green eventually.

The below is mostly agent-generated (I switched up to codex GPT-6-Astra ultra), with small tweaks:

Replies to review: #2993 (review)

When CI monitoring stopped, full CI had 104 successful jobs and two A100 jobs queued, with no failures; its final status gate was still pending.

Canonical links for new documentation pages

You're right. Building the latest layout caused Sphinx to emit canonical URLs pointing at production, so a new page could fail with a 404 before it was published. The earlier successful CI runs didn't add a page and missed this case.

The original choice of BUILD_LATEST=1 was to assemble a consistent set of documentation trees for all four packages using the existing build scripts. We can retain that layout while checking the pages built for this revision: the source-build helper now sets CUDA_PYTHON_DOCS_DOMAIN to the absolute file:// URI of artifacts/docs. Canonical links resolve to the actual local output, so we check them rather than excluding them. This override is confined to the link-checking build; the wheel-based build and published canonical URLs keep their existing behavior.

Fixed in 456f145. A real build of all four documentation trees included two temporary new cuda-core pages with a link between them. All 1,118 canonical links resolved locally. With external network requests disabled, pinned Lychee passed both the new-page check and a complete local-file and anchor scan of the rendered tree. Removing the new target then correctly failed with a missing-file error. The committed regression tests also cover the helper's domain setup for paths with and without spaces. The actual PR documentation run additionally covered external links and passed both link jobs on their first checker attempt, with zero errors or timeouts.

Checking the PR merge revision

Agreed: the PR check should include its integration with the base branch. The original explicit head checkout was intended to give generated GitHub source links an ordinary commit SHA that GitHub could resolve. That precaution was unnecessary: we verified that GitHub source links at the test-merge SHA resolve and pass the pinned Lychee version.

The workflow now uses the default checkout revision, matching the standalone pre-commit workflow. For a PR event this is GitHub's test merge with the base branch; manual and nightly runs use their selected revision. Generated source links still use the actual checked-out SHA.

Fixed in 081ded4. A separate Git fixture reproduced the concern: the PR added a link while main removed its target, producing a clean merge. Pinned Lychee passed at the PR head and failed at the merge revision. In the actual PR run, both checkout logs confirm test-merge SHA 692e123, combining main at 4c893de with PR head 081ded4. All three jobs passed, including rendered documentation with source links at that merge SHA.

The nightly integration and cache reader also passed on their first workflow attempts. The reader restored both exact producer snapshots, passed both link checks, and skipped cache publication.

Full CI was triggered at PR head 081ded4 to cover the complete package build/test plan, the wheel-based documentation build and preview deployment, and the final status gate. The documentation build and preview deployment have both passed.

Retry count and the job timeout

The repeated passes reuse successful external-link results; only unsuccessful external checks and local files need checking again. We don't have enough representative cold-cache runs to estimate retry frequencies, and we have actually exhausted all ten attempts on a persistent HTTP 429, so the rationale shouldn't depend on that being extremely rare.

The two limits bounds are complementary safeguards: ten attempts limit repeated checking, while 60 minutes limits the total job duration.

Ten attempts means the initial check plus nine retries. The cooldowns total 17 minutes: one minute before the first retry, then two minutes before each of the remaining eight. That leaves 43 minutes for setup, building the documentation, and all ten checking passes.

Our measurements suggest substantial room within that budget. Building the documentation took about seven minutes, and a cold rendered check took about five minutes. Subsequent checks reuse successful external results: retries during an observed persistent HTTP 429 took roughly 40-60 seconds each.

Using those timings, even nine retries would give roughly 7 + 5 + 9 + 17 = 38 minutes, plus setup overhead. Consistent with that estimate, an actual job exhausted all ten attempts in about 37 minutes.

@rwgk
rwgk marked this pull request as ready for review October 6, 2026 22:53
@rwgk

rwgk commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Codex — 2026-10-08T17:05:21-07:00 (PDT) · +2m 7s after Codex · +1h 24m since User

Implemented and pushed to PR 2993.

  • Three-pass retry limit; bounded 429 warnings; advisory nightly link failures with checker errors remaining fatal.
  • Repaired moved sample links and nightly documentation setup.
  • Local hooks, 220 tests plus 36 subtests, nightly producer, and cache reader passed.
  • Full CI passed all 107 jobs without reruns.

The description now includes the policy as its first expandable section and current validation evidence.

The worktree is clean, PR branches are synchronized, and 2993 remains Draft. I rebased immediately before pushing; main advanced while CI ran, which the description records.

@rwgk

rwgk commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Fable 5 re-review:

Verdict: the new retry and policy logic is correct as far as I can trace it, and the three commits since my last pass resolve most of what I raised. Nothing blocks merging on the PR's side. The two red checks are still main's stale lockfile, not this PR. The added-work answer is unchanged: no per-PR docs build, one new nightly source build.

What changed since the last review

The head moved from 585450e to 54ee0bb with three commits. The description now matches the code, including the validated head.

  • Retry bound dropped from 10 passes to 3, with 60 s and 120 s cooldowns. Each check now spends at most three minutes in outer cooldowns instead of up to 17.
  • Two failure policies. Full CI uses check: pass, or pass with a warning when only HTTP 429s remain on at most 10 distinct URLs, otherwise fail. Nightly uses cache-warm: any valid link failure is a warning and the cache is still published. Machinery failures stay hard failures under both.
  • Nightly builds with PIXI_FROZEN instead of PIXI_LOCKED, so a stale workspace lockfile no longer blocks the nightly rendered job.
  • Seven sample READMEs point at moved pages. I probed all seven new targets and they return 200. All five old targets I probed return 404, and the Nsight Compute anchor exists on the page.
  • Earlier nits fixed: the duplicate GITHUB_REF export, the nightly header comment, and the ambient-Python dependency, which is now documented at .github/actions/check-doc-links/action.yml:19.

Findings

  1. Nightly link rot is now invisible by default. Under cache-warm, a 404 in main's docs is a warning in a nightly job summary that nobody reads, and the next unrelated PR's full CI is what fails. That is the intended trade for keeping the nightly suite green, but it means the first person to hit a dead link is a random contributor. A cheap follow-up would be a nightly step that opens or updates a single tracking issue when the final report has permanent failures. ci/tools/retry_lychee.py:159

  2. A permanent failure in nightly stops the 429 retries too. When a 404 and some 429s coexist, the loop exits on the first pass, warns, and publishes the cache. The rate-limited URLs never get their second and third passes, so the cache stays colder than it could be for that day. The description acknowledges this. If it matters, the cache-warm policy could keep retrying while any transient failure remains. ci/tools/retry_lychee.py:197

  3. PIXI_FROZEN trades one failure mode for another. Frozen installs the committed lock without checking it against the manifest. If someone adds a docs dependency to the cuda_core manifest without relocking, the nightly builds against the old environment and fails or misbehaves inside Sphinx rather than at a clear lock check. The repo's separate lock-freshness check catches the drift, so this is acceptable. Worth knowing when reading a confusing nightly build log. .github/workflows/lychee.yml:42

  4. Still true from the last review: network-level failures without an HTTP code, such as connection resets and DNS errors, are never retried by the outer loop. Lychee's own three per-request retries remain the only cover. ci/tools/retry_lychee.py:47

  5. Still true: whether a top-level concurrency block applies to a called workflow is undocumented. Harmless because the nightly caller has its own group. .github/workflows/lychee.yml:32

  6. Still true: the red checks on this head come from main's stale cuda_core lockfile. The PR's validated base is the same commit that fails on main. Merge is safe since the nightly no longer depends on the lock check.

  7. Nit: the retry passes still add --mode task, which the first pass through lychee-action does not use. Harmless with JSON output.

  8. Nit: the pre-commit.ci block in .pre-commit-config.yaml:5 stays until the App is detached, as the description says.

Added work vs. before PR 2994

Unchanged from my earlier answer. Nothing new runs on a PR push. Full CI loses the Windows pre-commit job and gains a cheap authored lychee pass inside the existing Docs job. The nightly gains two ubuntu jobs, one of which builds docs from source once a day. The shorter retry bound makes the worst case cheaper than before: a fully rate-limited check now costs about three minutes of cooldown plus three passes instead of seventeen minutes plus ten passes.

Verified

  • All 220 CI-tool tests and 36 subtests pass locally with the nightly's invocation. The pre-commit hooks that apply to the changed files pass, including actionlint and ruff.
  • Traced every path through the retry loop: exit codes 1 and 3 stop without retry under both policies; mixed 404 plus 429 fails check on pass one; all-429 reaches the allowance only after three passes; the distinct-URL count deduplicates across source files; argparse rejects unknown policies as a machinery failure.
  • The build-docs caller passes no policy and gets check. The lychee workflow maps refresh-cache to cache-warm, so the nightly and a manual refresh warm, while a manual reader run enforces.
  • The new tests cover the 10 versus 11 boundary, cross-source deduplication, the seven non-429 shapes the allowance must reject, cache-warm warnings for permanent failures, and the CLI defaults. All carry the agent-authored marker.

@rwgk

rwgk commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Responses to the PR-specific findings in the Fable 5 re-review, for 54ee0bb:

  • 1. Nightly visibility — intentional tradeoff; possible follow-on. The failures remain visible as warning annotations, job summaries, and report artifacts, but agreed that those can go unnoticed. Nightly did not previously check these links, so this adds advisory detection. An unrelated PR can still be the first run to enforce a permanent failure. Automatically maintaining a tracking issue could be useful, but I would keep that notification behavior outside this PR.

  • 2. Mixed permanent/transient failures — keep the current policy. Correct: a 404 alongside 429s stops further passes, leaving a less complete cache. That is acceptable for the present goal of producing a useful partial cache. Successful checks are retained; unresolved URLs are not cached as successes. Continuing retries specifically for cache warming is a possible later optimization, with the same three-pass ceiling.

  • 3. PIXI_FROZEN — accepted tradeoff. Correct: frozen mode uses the committed documentation environment without verifying agreement with the manifest. Adding a documentation dependency still requires updating the lockfile. Build and checker failures remain fatal; frozen mode does not turn them into warnings. The fresh source documentation build was validated successfully on GitHub runners.

  • 4. Network errors — two clarifications. HTTP(S) timeouts are eligible for outer retries even without an HTTP status code. Also, Lychee’s native retry setting does not guarantee retries for every network error: its pinned implementation excludes non-timeout connection errors and recognizes some connection resets as retryable. I would retain the narrow outer classification here and expand it only against concrete failure examples and tests. See the outer classifier and native classifier.

  • 5. Concurrency — no change proposed. The nightly caller has its own concurrency control, so nightly scheduling does not depend on resolving the question about the called workflow’s top-level block. The link workflow’s block also controls direct manual dispatches. Caller and callee groups are deliberately distinct, and documentation-only tests are separated from full nightly runs.

  • 7. --mode task — already consistent. The initial pass uses task mode too. The pinned action defaults checkbox to true, which its entrypoint translates into --mode task. The retry helper supplies the same option explicitly. There is no mode difference to fix. See the default and initial invocation.

  • 8. pre-commit.ci configuration — intentionally retained. I want to minimize churn during the cutover. I’ll coordinate the Ruleset changes and App access removal with a repo admin shortly after merge. Removing the unused configuration can follow separately.

One clarification on added work: ordinary PR updates run the replacement Linux/Windows pre-commit jobs, but add no documentation build or Lychee pass. Full CI adds authored-link checking after its existing documentation build. Its cost depends on the cache: the measured authored Lychee execution was about 152 seconds cold versus 0.022 seconds warm, excluding setup. Nightly adds the fresh source build and cache production.

@rwgk
rwgk marked this pull request as ready for review October 9, 2026 04:12
@rwgk

rwgk commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

I'm a little concern about adding about ~9min of compute time to every PR

@mdboom This is fixed now; I also realized that my original, simple 10-retry idea doesn't work as intended when there are servers with long rate limits, so I changed the retry logic completely. This is documented in detail in the updated PR description.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: NVIDIA/cuda-python/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: aac4cddd-728c-44cb-9c0b-9d1f73764d3d
📥 Commits

Reviewing files that changed from the base of the PR and between 081ded4 and 54ee0bb.

📒 Files selected for processing (17)
  • .github/actions/check-doc-links/action.yml
  • .github/workflows/build-docs.yml
  • .github/workflows/ci-nightly.yml
  • .github/workflows/lychee.yml
  • .github/workflows/pre-commit.yml
  • CONTRIBUTING.md
  • ci/tools/retry_lychee.py
  • ci/tools/tests/test_retry_lychee.py
  • cuda_core/pixi.toml
  • lychee.toml
  • samples/cuda_bindings/3_CUDA_Features/globalToShmemAsyncCopy/README.md
  • samples/cuda_core/binarySearch/README.md
  • samples/cuda_core/cudaComputeLambdas/README.md
  • samples/cuda_core/launchConfigTuning/README.md
  • samples/cuda_core/pageRank/README.md
  • samples/cuda_core/simplePrint/README.md
  • samples/cuda_core/tmaTensorMap/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • lychee.toml

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread CONTRIBUTING.md
Comment on lines +232 to +233
If the hook isn't installed, `pre-commit run` will print a visible warning
reminding you to run `pre-commit install`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

important: Correct the hook-installation warning. pre-commit run does not check whether the Git hook is installed. Its pre-commit install reminder appears only when an all-files run with diff-on-failure enabled fails after hooks modify files; the documented command at Line 223 does not enable that option. Replace this claim with a direct instruction to run pre-commit install. (github.com)

Comment thread cuda_core/pixi.toml
cmd = ["$PIXI_PROJECT_ROOT/docs/build_docs.sh", "latest-only"]
default-environment = "docs"

[target.linux.tasks.docs-build-all-latest]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

set -o pipefail
printf '%s\n' '--- changed paths ---'
git diff --name-status 106da2fdcbf4679c0fe1373f236def595c39fc25 54ee0bb2641c7d5a349b1316264eed510350072c -- cuda_core/pixi.toml cuda_core/pixi.lock ci/pixi-version.env
printf '%s\n' '--- focused manifest diff ---'
git diff --unified=20 106da2fdcbf4679c0fe1373f236def595c39fc25 54ee0bb2641c7d5a349b1316264eed510350072c -- cuda_core/pixi.toml
printf '%s\n' '--- lockfile diff stat and content status ---'
git diff --stat 106da2fdcbf4679c0fe1373f236def595c39fc25 54ee0bb2641c7d5a349b1316264eed510350072c -- cuda_core/pixi.lock
git diff --quiet 106da2fdcbf4679c0fe1373f236def595c39fc25 54ee0bb2641c7d5a349b1316264eed510350072c -- cuda_core/pixi.lock; printf 'lockfile_diff_exit=%s\n' "$?"
printf '%s\n' '--- relevant manifest task ---'
nl -ba cuda_core/pixi.toml | sed -n '300,325p'
printf '%s\n' '--- pinned Pixi version ---'
nl -ba ci/pixi-version.env

Repository: NVIDIA/cuda-python

Length of output: 3200


important: Update the corresponding Pixi lockfile.

This PR changes cuda_core/pixi.toml but does not change cuda_core/pixi.lock. Regenerate the lockfile with Pixi v0.73.0 from ci/pixi-version.env and commit any changed bytes. If regeneration produces identical bytes, confirm that result.

Source: Coding guidelines

@rwgk
rwgk force-pushed the rwgk/maint/lychee-pr-nightly branch from 54ee0bb to d3091d7 Compare October 9, 2026 16:23
@rwgk rwgk mentioned this pull request Oct 9, 2026
2 tasks done
@rwgk

rwgk commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Converting back to draft, until the pixi freshness situation is more stable (see #3065).

@rwgk
rwgk marked this pull request as draft October 9, 2026 18:01
@rwgk
rwgk force-pushed the rwgk/maint/lychee-pr-nightly branch from d3091d7 to 7083864 Compare October 9, 2026 22:39
@rwgk
rwgk force-pushed the rwgk/maint/lychee-pr-nightly branch from 7083864 to e8c438b Compare October 9, 2026 22:43

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD CI/CD infrastructure cuda.bindings Everything related to the cuda.bindings module cuda.core Everything related to the cuda.core module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants